﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using Csla;
using Csla.Core;

using NetFrameworkExtensions.DataLayer;

namespace NetFrameworkExtensions.BusinessLayer.Csla4
{
  [Serializable]
  public abstract class CslaBusinessBase<TEntity, TIdentified, TIdentifier> :
    BusinessBase<TIdentified>,
    ICslaBusinessObject<TEntity, TIdentified, TIdentifier>,
    IPropertyContainer,
    IAccessibleObject,
    IBusinessBaseMark,
    IFormatted
    where TIdentified : CslaBusinessBase<TEntity, TIdentified, TIdentifier>
    where TEntity : class, IIdentified
    where TIdentifier : IEquatable<TIdentifier>
  {
    #region [ Business Methods ]
    public static readonly PropertyInfo<TIdentifier> IdentifierProperty = RegisterProperty<TIdentifier>(c => c.Identifier);
    public TIdentifier Identifier
    {
      get { return GetProperty(IdentifierProperty); }
      set { LoadProperty(IdentifierProperty, value); }
    }

    public static readonly PropertyInfo<byte[]> TimeStampProperty = RegisterProperty<byte[]>(c => c.Timestamp);
    public byte[] Timestamp
    {
      get { return GetProperty(TimeStampProperty); }
      set { LoadProperty(TimeStampProperty, value); }
    }
    public static readonly PropertyInfo<DateTime?> DeletionDateProperty = RegisterProperty<DateTime?>(c => c.DeletionDate);
    public DateTime? DeletionDate
    {
      get { return GetProperty(DeletionDateProperty); }
      private set { LoadProperty(DeletionDateProperty, value); }
    }
    #endregion

    #region [ Formatting ]

    public abstract string Formatted { get; }

    public override string ToString()
    {
      return Formatted;
    }

    protected abstract IEnumerable<string> GetFormattingProperties();

    protected override void OnPropertyChanged(string propertyName)
    {
      base.OnPropertyChanged(propertyName);

      if (GetFormattingProperties().Contains(propertyName))
        OnPropertyChanged("Formated");
    }

    #endregion

    #region [ DataAccess ]
    protected virtual void CommonFetch(IIdentifiedData data)
    {
      if (data.IdentifierType == typeof(TIdentifier))
        Identifier = (TIdentifier)data.Identifier;
      else
        throw new ArgumentException("");

      var auditable = data as IDateTimeAuditableData;
      if (auditable != null)
        DeletionDate = auditable.DeletionDate;

      var concurrent = data as IConcurrentData;
      if (concurrent != null)
        Timestamp = concurrent.Timestamp;
    }
    #endregion

    #region [ IBusinessBase ]
    IBusinessBase IBusinessBase.Parent
    {
      get { return Parent as IBusinessBase; }
    }
    #endregion

    #region [ IIdentified ]
    Type IIdentified.IdentifiedType {
      get
      {
        return typeof(TIdentified);
      }
    }

    Type IIdentified.IdentifierType
    {
      get
      {
        return typeof(TIdentifier);
      }
    }

    object IIdentified.Identifier
    {
      get
      {
        return this.Identifier;
      }
      set
      {
        this.Identifier = (TIdentifier)value;
      }
    }
    #endregion

    #region [ IPropertyContainer ]
    protected virtual void AssignProperty<T>(IPropertyInfo property, T value, bool condition = true)
    {
      if (condition)
        this.SetProperty(property, value);
    }
    private void SetPropertiesStart(TEntity item)
    {
      bool sourceIsDirty = item is ITrackStatus && ((ITrackStatus)item).IsDirty;
      Debug.Assert(item is ITrackStatus && !((ITrackStatus)item).IsDeleted);

      bool thisWasDirty = IsDirty;
      bool thisWasNew = IsNew;
      Debug.Assert(!IsDeleted);

      if (item is IConcurrent)
      LoadProperty(TimeStampProperty, ((IConcurrent)item).Timestamp);
      SetProperties(item);

      if (thisWasNew)
      {
        // Then this stays as new
        Debug.Assert(IsNew);
      }
      else if (thisWasDirty)
      {
        // Then this stays as dirty
        Debug.Assert(IsDirty);
      }
      else if (sourceIsDirty)
      {
        // Mark this as dirty, although it should be marked anyway
        MarkDirty();
      }
      else
        if (!sourceIsDirty && !thisWasDirty)
        {
          // this was clean and notified object is clean, so even if some properties changed, this remains clean
          this.MarkOld();
        }
    }
    protected abstract void SetProperties(TEntity item);
    void IPropertyContainer.SetProperties(IIdentified item)
    {
      var it = item as TEntity;
      if (it != null)
        SetPropertiesStart(it);
      else
      {
        var g = (IConcurrent)item;
        if (g != null)
          LoadProperty(TimeStampProperty, g.Timestamp);
      }
    }

    #endregion

    #region [ IAccessibleObject ]
    public abstract bool CanUpdate { get; }

    public abstract bool CanDelete { get; }
    #endregion

    #region [ IBusinessBaseMark ]
    void IBusinessBaseMark.MarkAsChild()
    {
      base.MarkAsChild();
    }
    void IBusinessBaseMark.MarkClean()
    {
      base.MarkClean();
    }
    void IBusinessBaseMark.MarkDeleted()
    {
      base.MarkDeleted();
    }
    void IBusinessBaseMark.MarkDirty()
    {
      base.MarkDirty();
    }
    void IBusinessBaseMark.MarkDirty(bool supressEvents)
    {
      base.MarkDirty(supressEvents);
    }
    void IBusinessBaseMark.MarkNew()
    {
      base.MarkNew();
    }
    void IBusinessBaseMark.MarkOld()
    {
      base.MarkOld();
    }
    void IBusinessBaseMark.RecoverDeleted()
    {
      if (IsDeleted)
      {
        if (IsNew)
          MarkNew(); // Set IsDeleted = false, IsNew = true and IsDirty = true
        else
        {
          MarkNew(); // Set IsDeleted = false, IsNew = true and IsDirty = true
          MarkOld(); // Set IsNew = false and IsDirty = false
          MarkDirty(); // Set IsDirty = true
        }
      }
    }
    #endregion

    #region [ Implementation ]

    IEnumerable<IPropertyInfo> ICslaBusinessObject.GetRegisteredProperties()
    {
      return FieldManager.GetRegisteredProperties();
    }

    #endregion
  }
}
